package cn.com.ctcloud.microservice.dynamicds;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.AbstractDataSource;

import cn.com.ctcloud.microservice.dynamicds.config.DynamicDatasourceConfigProperties;
import cn.com.ctcloud.microservice.dynamicds.database.druid.DruidDBConfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Title: 分布式动态数据源路由
 * <p>
 * Description: 动态数据源实现类
 * <p>
 * 客户端使用路由时，在Spring Boot 启动类上添加@EnableDynamicDS注解
 * <p>
 * Copyright: Copyright (c) 2008
 * <p>
 * Company: Liaoning Open Data Communication Co.,Ltd.
 * 
 * @author lihui
 * @version 1.0.0
 */
public class SaasDynamicDatasource extends AbstractDataSource {

	private Map<String, DataSource> dataSourceMap = new WeakHashMap<String, DataSource>(0);

	private GeneralAttributes generalAttributes;

	private DruidDBConfig druidDBConfig;

	private Map<String, TenantDatasourceAttributes> tenantDatasourceAttributesMap;

	public void setDsProperties(DynamicDatasourceConfigProperties dsProperties, DruidDBConfig druidDBConfig) {
		this.druidDBConfig = druidDBConfig;
		parse(dsProperties);
	}

	/**
	 * 负责将动态数据源的配置信息解析
	 */
	private void parse(DynamicDatasourceConfigProperties dsProperties) {
		Map<String, String> generalMap = dsProperties.getGeneral();
		generalAttributes = new GeneralAttributes();
		generalAttributes.setDefaultTenant(generalMap.get("defaultTenant"));

		Map<String, Map<String, String>> tenants = dsProperties.getTenants();
		tenantDatasourceAttributesMap = new HashMap<String, TenantDatasourceAttributes>();

		for (String organCode : tenants.keySet()) {
			Map<String, String> tenantDSAttr = tenants.get(organCode);
			TenantDatasourceAttributes tenantDatasourceAttributes = new TenantDatasourceAttributes();
			tenantDatasourceAttributes.setType(tenantDSAttr.get("type"));
			tenantDatasourceAttributes.setDriverClassName(tenantDSAttr.get("driverClassName"));
			tenantDatasourceAttributes.setUrl(tenantDSAttr.get("url"));
			tenantDatasourceAttributes.setUserName(tenantDSAttr.get("userName"));
			tenantDatasourceAttributes.setPassword(tenantDSAttr.get("password"));
			tenantDatasourceAttributesMap.put(organCode, tenantDatasourceAttributes);
		}

	}

	@Data
	@NoArgsConstructor
	@AllArgsConstructor
	private static class GeneralAttributes {
		private String defaultTenant;
	}

	@Data
	@NoArgsConstructor
	@AllArgsConstructor
	private static class TenantDatasourceAttributes {
		private String type;
		private String driverClassName;
		private String url;
		private String userName;
		private String password;
	}

	/**
	 * 当一个请求需要访问数据源时，它会根据OrganCodeHolder.getOrganCode()得到当前请求线程的orgCode，尝试从缓存中按照organCode查找是否存在对应的数据源，如果不存在，则会根据配置信息构造一个HikariDataSource数据源实例塞入缓存中并得到Connection，然后把得到的Connection对象返回
	 */
	@Override
	public Connection getConnection() throws SQLException {
		String currentOrganCode = OrganCodeHolder.getOrganCode();
		if (currentOrganCode == null) {
			currentOrganCode = generalAttributes.getDefaultTenant();
		}
		if (!tenantDatasourceAttributesMap.containsKey(currentOrganCode)) {
			throw new SQLException(
					"there is no datasource configuration for the organization with code " + currentOrganCode);
		}
		DataSource ds = dataSourceMap.get(currentOrganCode);
		//double check
		if (ds == null) {
			TenantDatasourceAttributes tenantDatasourceAttributes = tenantDatasourceAttributesMap.get(currentOrganCode);
			synchronized (this) {
				ds = dataSourceMap.get(currentOrganCode);
				if (ds == null) {

					ds = druidDBConfig.createDataSource(tenantDatasourceAttributes.getDriverClassName(),
							tenantDatasourceAttributes.getUrl(), tenantDatasourceAttributes.getUserName(),
							tenantDatasourceAttributes.getPassword());

					dataSourceMap.put(currentOrganCode, ds);
				}
			}
		}
		return ds.getConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		throw new SQLFeatureNotSupportedException();
	}
}
